package zy.catalog;

import com.gargoylesoftware.htmlunit.*;
import com.gargoylesoftware.htmlunit.html.*;
import com.google.gson.Gson;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.cli.*;
import org.apache.commons.io.FileExistsException;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.apache.http.HttpHost;
import org.apache.http.conn.routing.HttpRoutePlanner;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.DefaultProxyRoutePlanner;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;

import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/*
 * move video file under the special folder recursively, and catalog video into folder
 *
 */
@Slf4j
public class App {
    static enum Command {
	    export, sync
    }

	public static final String FILE_NAME = ".crc32";

	static Gson gson = new Gson();

    public static String[] EXTENSIONS = new String[] {"avi", "rmvb", "wmv", "mp4", "mkv"};

    static {
	    //Creating an HttpHost object for proxy
	    HttpHost proxyhost = new HttpHost("127.0.0.1", 1080);
	    //Creating an HttpHost object for target
	    HttpHost targethost = new HttpHost("google.com");
	    //creating a RoutePlanner object
	    HttpRoutePlanner routePlanner = new DefaultProxyRoutePlanner(proxyhost);
	    //Setting the route planner to the HttpClientBuilder object
	    HttpClientBuilder clientBuilder = HttpClients.custom();
	    clientBuilder = clientBuilder.setRoutePlanner(routePlanner);
	    //Building a CloseableHttpClient
	    httpclient = clientBuilder.build();
    }
    static CloseableHttpClient httpclient;
	static Pattern codePattern = Pattern.compile("[A-Za-z0-9_]+-?[0-9]+(c)?");//Pattern.compile("[0-9]*[A-Za-z]+-?[0-9]+");//Pattern.compile("[A-Za-z0-9_]+-?[0-9]+");
	static Pattern codeIdPattern = Pattern.compile("[0-9]*[A-Za-z]+-?");
	static Pattern wwwPattern = Pattern.compile("(www.)?[A-Za-z0-9_]+(.cc|.com|.me|.xyz|.net)");
	public static String removeWWW(String input) {
		return input.replaceFirst(wwwPattern.pattern(), "");
	}

	public static class Code {
		public String platform;
		public int id;
		public boolean isHd;
		public boolean isChinese;
		public boolean isCensored;

		@Override
		public String toString() {
			String str = platform + "-" + id;
			if (isChinese)
				str += ".c";
			if (isHd)
				str += ".hd";
			if (isCensored)
				str += ".uncensored";
			return str.toUpperCase();
		}

		public int getScore() {
			int score = 0;
			if (isHd)
				score += 3;
			if (isChinese)
				score += 1;
			if (isCensored)
				score += 5;
			return score;
		}

		public boolean equalsID(Code o) {
			return this.platform.equals(o.platform) && this.id == o.id;
		}
	}

	public static Code getCode(String input) throws Exception {
		input = input.toLowerCase();
		input = removeWWW(input);
		String[] parts = new String[] {input.replace("mp4", "")};//.replace(".", " ").split(" ");
		List<String> matches = new ArrayList<>();
		for (String part : parts) {
			Matcher matcher = codePattern.matcher(part);
			while (matcher.find()) {
				matches.add(matcher.group());
			}
		}
		if (matches.size() == 1) {
			String match = matches.get(0);
			Code code = new Code();
			if (match.endsWith("c")) {
				match = match.substring(0, match.length() - 1);
				code.isChinese = true;
			}
			String[] paths = match.split("-");
			if (paths.length == 2) {
				code.platform = paths[0];
				code.id = Integer.valueOf(paths[1]).intValue();
			} else {
				Matcher m = codeIdPattern.matcher(match);
				if (m.find()) {
					String prefix = m.group();
					if (prefix.endsWith("-"))
						prefix = prefix.substring(0, prefix.length() - 1);
					code.platform = match.substring(0, prefix.length());
					code.id = Integer.valueOf(match.substring(prefix.length(), match.length())).intValue();
				} else {
					throw new Exception("unknown code");
				}
			}
			/*if (Arrays.stream(parts).filter(p -> p.equals("hd")).count()
					+ Arrays.stream(parts).filter(p -> p.equals("fhd")).count() > 0)
				code.isHd = true;
			if (Arrays.stream(parts).filter(p -> p.equals("uncensored")).count() > 0)
				code.isCensored = true;*/
			if (input.indexOf(".hd") > -1 || input.indexOf(".fhd") > -1|| input.indexOf("-hd") > -1 || input.indexOf("-fhd") > -1)
				code.isHd = true;
			if (input.indexOf(".uncensored") > -1 || input.indexOf("-uncensored") > -1)
				code.isCensored = true;
			return code;
		} else {
			throw new Exception("unknown code " + input + " >> " + matches.stream().collect(Collectors.joining(" ")));
		}
	}

    /**
	 * 1.download torrent into subfolder with high prior about【破壊版】, and create a log under each folder
	 *
     * java -jar catalog.jar --mode=sync|export --source=./ --target=./
     * sh $export_shell
     *
     * @param args
     * @throws IOException
     */
    public static void main(String[] args) throws Exception {
        Option sourceDirOption = Option.builder( "source" ).hasArg(true).required(true).desc("").build();
	    Option targetDirOption = Option.builder( "target" ).hasArg(true).required(true).desc("").build();
	    Option countOption = Option.builder( "count" ).hasArg(true).required(false).desc("").build();
        Option renameOption = Option.builder( "rename" ).required(false).desc("").build();
        // create the parser
        CommandLineParser parser = new GnuParser();
		// parse the command line arguments
		CommandLine line = parser.parse(new Options().addOption(targetDirOption).addOption(sourceDirOption).addOption(renameOption).addOption(countOption), args);
		File sourceDir = new File(line.getOptionValue(sourceDirOption.getOpt()));
		File targetDir = new File(line.getOptionValue(targetDirOption.getOpt()));
		Collection<File> targetFiles = FileUtils.listFiles(targetDir, EXTENSIONS, true);
		// format existed files
		/*for (File targetFile : targetFiles) {
			try {
				String targetFileName = targetFile.getName();
				String extension = FilenameUtils.getExtension(targetFileName);
				Code code = getCode(targetFileName.substring(0, targetFileName.length() - ".".length() - extension.length()));
				String correctFileName = code + "." + extension;
				if (!correctFileName.equals(targetFileName)) {
					File destFile = new File(targetFile.getParentFile(), correctFileName);
					if (destFile.exists()) {
						if (FileUtils.sizeOf(targetFile) > FileUtils.sizeOf(destFile)) {
							log.debug("move {} to {}", targetFile, destFile);
							try {
								FileUtils.moveFile(targetFile, destFile);
							} catch (FileExistsException exception) {
								delete(targetFile);
							}
						} else {
							delete(targetFile);
						}
					} else {
						log.debug("move {} to {}", targetFile, destFile);
						FileUtils.moveFile(targetFile, destFile);
					}
				}
			} catch (Exception exception) {
				log.error(targetFile.getPath(), exception);
			}
		}*/
		/*remove duplicate code*/
		log.info("***************************");
		log.info("*** SCAN DUPLICATE CODE ***");
		log.info("***************************");
		Collection<File> codes = FileUtils.listFiles(targetDir, new String[]{"mp4"}, true);
		Map<Code, File> groups = new HashMap<>();
		for (Iterator<File> iterator = codes.iterator(); iterator.hasNext(); ) {
			File file = iterator.next();
			try {
				Code code = App.getCode(file.getName());
				if (groups.containsKey(code)) {
					log.warn("duplicate code {}, path {}", file, code);
				} else {
					groups.put(code, file);
				}
			} catch (Exception ex) {
			}
		}

		List<File> actorFolders = Arrays.stream(targetDir.listFiles()).collect(Collectors.toList());

		downloadTorrent(actorFolders);

//		catalogMovie(sourceDir, targetDir, actorFolders);
	}

	private static void catalogMovie(File sourceDir, File targetDir, List<File> actorFolders) {
		log.info("***************************");
		log.info("***** CATALOG MOVIES ******");
		log.info("***************************");
		Collection<File> sourceFiles = FileUtils.listFiles(sourceDir, EXTENSIONS, true);
		for (Iterator<File> iterator = sourceFiles.iterator(); iterator.hasNext(); ) {
			File sourceFile = iterator.next();
			if (sourceFile.exists() == false || FileUtils.sizeOf(sourceFile) < 500 * 1000 * 1000) {
				continue;
			}
			log.debug("checking " + sourceFile);
			try {
				Code code = getCode(FilenameUtils.getName(sourceFile.getName()));
				log.info("get code {}", code);
				List<Element> rows = search(getSearchLocation(code.platform + " " + String.format("%03d" , code.id)), 1, "div#threadlist li > h3", false);
				Elements elements = new Elements();
				elements.addAll(rows);
				log.info("searched {} rows", elements.text());
				List<Element> sortedRows = rows.stream().sorted(new Comparator<Element>() {
					@Override
					public int compare(Element o1, Element o2) {
						int o1Score = 0, o2Score = 0;
						try {
							if (App.getCode(o1.text()).platform.equals(code.platform) && App.getCode(o1.text()).id == code.id)
								o1Score = 100;
						} catch (Exception exception) {
						}
						try {
							if (App.getCode(o2.text()).platform.equals(code.platform) && App.getCode(o2.text()).id == code.id)
								o2Score = 100;
						} catch (Exception exception) {
						}
						if (o1.text().indexOf("【破壊版】") > -1)
							o1Score += 2;
						if (o2.text().indexOf("【破壊版】") > -1)
							o2Score += 2;
						if (o1.text().indexOf("高清") > -1)
							o1Score += 1;
						if (o2.text().indexOf("高清") > -1)
							o2Score += 1;
						return o1Score - o2Score;
					}
				}).collect(Collectors.toList());
				boolean isMoved = false;
				f:for (Element row : rows) {
					String relatedWords = row.text();
					for (File actorFolder : actorFolders) {
						if (actorFolder.isDirectory() && relatedWords.contains(actorFolder.getName())) {
							move(sourceFile, code.toString(), actorFolder);
							isMoved = true;
							break f;
						}
					}
				}
				if (!isMoved)
					move(sourceFile, code.toString(), new File(targetDir.getParentFile(), "未归类"));
			} catch (Exception e) {
				log.error("", e);
			}
		}
	}

	private static void downloadTorrent(List<File> actorFolders) throws Exception {
		log.info("***************************");
		log.info("*** DOWNLOAD TORRENTS ****");
		log.info("***************************");
//			Collections.shuffle(actorFolders);
		// prior actor
		actorFolders.sort(new Comparator<File>() {
			@Override
			public int compare(File o1, File o2) {
				int o1Score = new File(o1, ".torrent.history").exists() ? 1 : 0;
				int o2Score = new File(o2, ".torrent.history").exists() ? 1 : 0;
				return o1Score - o2Score;
			}
		});
		for (File actorFolder : actorFolders) {
			File[] torrents = actorFolder.listFiles(new FilenameFilter() {
				@Override
				public boolean accept(File dir, String name) {
					return name.endsWith(".torrent");
				}
			});
			for (File torrent : torrents) {
				File[] videos = actorFolder.listFiles(new FilenameFilter() {
					@Override
					public boolean accept(File dir, String name) {
						try {
							return !name.endsWith(".torrent") && getCode(torrent.getName()).equalsID(getCode(name));
						} catch (Exception exception) {
							return false;
						}
					}
				});
				if (videos.length > 0) {
					torrent.delete();
					log.debug("has video {}, also has torrent {}", videos[0], torrent);
				}
			}
		}
		for (File actorFolder : actorFolders) {
			log.info("handle {}", actorFolder);
			File updateFile = new File(actorFolder, ".torrent.update");
			updateFile.createNewFile();
			boolean isAlreadyUpdated = false;
			SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
			try {
				Date date = simpleDateFormat.parse(FileUtils.readFileToString(updateFile, "utf-8"));
				if (DateUtils.addDays(date, 7).after(Calendar.getInstance().getTime())) {
					isAlreadyUpdated = true;
				}
			} catch (Exception ex) {
			}

			if (isAlreadyUpdated)
				continue;
			List<Element> rows = search(getSearchLocation(actorFolder.getName()), 1, "div#threadlist li > h3 > a", true);
			Collections.reverse(rows);
			f:for (Iterator<Element> iterator = rows.iterator(); iterator.hasNext(); ) {
				Element href = iterator.next();
				String detailURL = searchHome.getProtocol() + "://" + searchHome.getHost() + "/" + href.attr("href");
				log.info("open detail page {}", detailURL);
				try {
					Page detailPage = visit(detailURL);
					switch (detailPage.getWebResponse().getStatusCode()) {
						case 200:
							Document doc = Jsoup.parse(detailPage.getWebResponse().getContentAsString());
							Elements downloads = doc.select("div#postlist p.attnm > a");
							if (downloads.size() > 0) {
								String torrentURL = searchHome.getProtocol() + "://" + searchHome.getHost() + "/" + downloads.get(0).attr("href");
								Page torrentPage = visit(torrentURL);
								switch (torrentPage.getWebResponse().getStatusCode()) {
									case 200:
										// https://www.kan224.com/forum.php?mod=attachment&aid=MzA0MzgwfGZjNDcwZTBlfDE2MjY2MDYwMzB8MHwzMTkyODg%3D
										String filename = downloads.get(0).text();
//										String filename = torrentPage.getWebResponse().getResponseHeaderValue("Content-Disposition");
//										filename = filename.substring("attachment; filename=\"".length(), filename.length() - "\"".length());
										byte[] torrentBytes = IOUtils.toByteArray(torrentPage.getWebResponse().getContentAsStream());
										try {
											final Code downloadFileCode = App.getCode(filename);
											log.info("download filename " + filename + " to " + downloadFileCode);
											List<File> otherHigherScoreFiles = Arrays.stream(actorFolder.listFiles()).filter(file -> file.getName().startsWith(".") == false).filter(otherFile -> {
												try {
													Code otherFileCode = App.getCode(otherFile.getName());
													return otherFileCode.equalsID(downloadFileCode) && otherFileCode.getScore() > downloadFileCode.getScore();
												} catch (Exception exception) {
													log.error("", exception);
													return false;
												}
											}).collect(Collectors.toList());
											otherHigherScoreFiles.forEach(other -> {
												log.info("get higher {}", other.getPath());
											});
											Arrays.stream(actorFolder.listFiles()).filter(file -> file.getName().endsWith(".torrent")).filter(otherFile -> {
												try {
													Code otherFileCode = App.getCode(otherFile.getName());
													return otherFileCode.equalsID(downloadFileCode) && otherFileCode.getScore() < downloadFileCode.getScore();
												} catch (Exception exception) {
													log.error("", exception);
													return false;
												}
											}).collect(Collectors.toList()).forEach(lowerFile -> {
												log.info("delete {}", lowerFile.getPath());
												lowerFile.delete();
											});
											if (otherHigherScoreFiles.size() == 0) {
												writeTorrent(new File(actorFolder, downloadFileCode + ".torrent"), torrentBytes);
											}
										} catch (Exception exception) {
											log.error("", exception);
											writeTorrent(new File(actorFolder, filename), torrentBytes);
										}
										break;
									default:
										log.error("fail to download torrent {}, status={}", torrentURL, torrentPage.getWebResponse().getStatusCode());
										break;
								}
							}
							break;
						default:
							log.error("status={}, {}", detailPage.getWebResponse().getStatusCode(), detailURL);
							break;
					}
				} catch (Exception ex) {
					Thread.sleep(30000L);
					log.error("open page {}", detailURL, ex);
				}
			}
			FileUtils.write(updateFile, simpleDateFormat.format(Calendar.getInstance().getTime()), "utf-8");
		}
	}

	private static Page visit(String detailURL) throws IOException {
		try (WebClient webClient = new WebClient(BrowserVersion.CHROME);) {
			webClient.getOptions().setCssEnabled(false);
			webClient.getOptions().setJavaScriptEnabled(false);
			return webClient.getPage(detailURL);
		}
	}

	private static void writeTorrent(File torrentFile, byte[] torrentBytes) throws IOException {
		File historyFile = new File(torrentFile.getParentFile(), ".torrent.history");
		historyFile.createNewFile();
		HashSet<String> histories = new HashSet<String>(FileUtils.readLines(historyFile, "utf-8"));
		if (histories.add(torrentFile.getName())) {
			FileUtils.writeByteArrayToFile(torrentFile, torrentBytes);
			log.info("write torrent {}", torrentFile.getPath());
			FileUtils.writeLines(historyFile, histories);
			log.info("write history {}", historyFile.getPath());
		}
	}

	private static void move(File sourceFile, String code, File catalogFolder) throws IOException {
		File destFile = new File(catalogFolder, code + "." + FilenameUtils.getExtension(sourceFile.getName()));
		log.info("move {} to {}", sourceFile.getPath(), destFile.getPath());
		try {
			FileUtils.moveFile(sourceFile, destFile);
		} catch (FileExistsException ex) {
			log.warn("", ex);
		}
		delete(sourceFile);
	}

	static long lastSearchMS = 0L;
	private static List<Element> search(URL location, int pageIndex, String cssQuery, boolean isGetAll) throws Exception {
		long sleep = Math.max(0L, 1000L * 11 - (System.currentTimeMillis() - lastSearchMS));
		log.debug("sleep {}", sleep);
		Thread.sleep(sleep);

		List<Element> result = new ArrayList<>();
		try (WebClient webClient = new WebClient(BrowserVersion.CHROME);) {
			webClient.getOptions().setCssEnabled(false);
			webClient.getOptions().setJavaScriptEnabled(false);
			String url = location.toString() + "&page=" + pageIndex;
			log.info("search {}", url);
			Document doc = Jsoup.parse(webClient.getPage(url).getWebResponse().getContentAsString());
			lastSearchMS = System.currentTimeMillis();
			Elements elements = doc.select(cssQuery);
			result.addAll(elements);
			if (elements.size() > 0 && isGetAll) {
				result.addAll(search(location, pageIndex + 1, cssQuery, isGetAll));
			}
			return result;
		}
	}

	static URL searchHome;

	static {
		try {
			searchHome = new URL("https://www.vc889.com/search.php?mod=forum&searchid=2353&orderby=lastpost&ascdesc=desc&searchsubmit=yes&kw=%E5%AE%AE%E8%A5%BF%E3%81%B2%E3%81%8B%E3%82%8B");
		} catch (MalformedURLException e) {
			e.printStackTrace();
		}
	}

	public static URL getSearchLocation(String keyword) throws IOException {
		try (WebClient webClient = new WebClient(BrowserVersion.CHROME);) {
			webClient.getOptions().setCssEnabled(false);
			webClient.getOptions().setJavaScriptEnabled(false);
			HtmlPage page = webClient.getPage(new WebRequest(searchHome));
			int statusCode = page.getWebResponse().getStatusCode();
			HtmlInput htmlInput = page.getElementById("scform_srchtxt") == null ?
					(HtmlInput) page.getElementById("scbar_txt") : (HtmlInput) page.getElementById("scform_srchtxt");
			htmlInput.setValue(keyword);
			DomElement btn = page.getElementById("scbar_btn") == null ?
					page.getElementById("scform_submit") : page.getElementById("scbar_btn");
			WebResponse resp = btn.click().getWebResponse();
			log.debug("{}", resp.getWebRequest().getUrl());
			log.debug("{}", resp.getContentAsString());
			return resp.getWebRequest().getUrl();
		}
	}

	// MGT-116~nyap2p.com.mp4
	private static String getLisence(File file) {
		String filename = file.getName();
		String extension = FilenameUtils.getExtension(filename);
		filename = filename.substring(0, filename.length() - extension.length());
		filename.indexOf("~");
		return filename;
	}

	private static boolean hasBiggerFile(File sourceFile) {
		Collection<File> files = FileUtils.listFiles(sourceFile, null, true);
		for (Iterator<File> iterator = files.iterator(); iterator.hasNext(); ) {
			File next = iterator.next();
			if (next.length() > 300 * 1000 * 1000L)
				return true;
		}
		return false;
	}

	static String lastInput;

	private static void delete(File sourceFile) throws IOException {
		try (Scanner scanner = new Scanner(System.in);) {
			while (true) {
				if (!("a").equals(lastInput)) {
					log.info("Delete " + sourceFile.toPath() + " y or n ?");
					lastInput = scanner.nextLine();
				}
				if (lastInput.equals("y") || lastInput.equals("a")) {
					if (sourceFile.exists()) {
						sourceFile.delete();
					}
					File parentFile = sourceFile.getParentFile();
					if (!hasBiggerFile(parentFile)) {
						FileUtils.deleteDirectory(parentFile);
						parentFile.delete();
						log.info("delete " + parentFile.toPath());
					}
					break;
				} else if (lastInput.equals("n")) {
					break;
				}
			}
		}
	}
}
